package com.linb.pan.server.modules.file.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.linb.pan.core.constants.BPanConstants;
import com.linb.pan.core.exception.BPanBusinessException;
import com.linb.pan.core.utils.FileUtil;
import com.linb.pan.core.utils.IdUtil;
import com.linb.pan.server.common.stream.channel.PanChannels;
import com.linb.pan.server.common.stream.event.file.DeleteFileEvent;
import com.linb.pan.server.common.stream.event.search.FileSearchEvent;
import com.linb.pan.server.common.utils.HttpUtil;
import com.linb.pan.server.modules.file.constants.FileConstants;
import com.linb.pan.server.modules.file.context.*;
import com.linb.pan.server.modules.file.converter.FileConverter;
import com.linb.pan.server.modules.file.entity.BPanFile;
import com.linb.pan.server.modules.file.entity.BPanUserFile;
import com.linb.pan.server.modules.file.enums.DelFlagEnum;
import com.linb.pan.server.modules.file.enums.FileTypeEnum;
import com.linb.pan.server.modules.file.enums.FolderFlagEnum;
import com.linb.pan.server.modules.file.mapper.BPanUserFileMapper;
import com.linb.pan.server.modules.file.po.FolderTreeNodeVO;
import com.linb.pan.server.modules.file.service.IFileChunkService;
import com.linb.pan.server.modules.file.service.IFileService;
import com.linb.pan.server.modules.file.service.IUserFileService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.linb.pan.server.modules.file.vo.*;
import com.linb.pan.storage.engine.core.StorageEngine;
import com.linb.pan.storage.engine.core.context.ReadFileContext;
import com.linb.pan.stream.core.IStreamProducer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户文件信息表 服务实现类
 * </p>
 *
 * @author 林云
 * @since 2023-09-02
 */
@Service
@Slf4j
public class UserFileServiceImpl extends ServiceImpl<BPanUserFileMapper, BPanUserFile> implements IUserFileService {

    @Autowired
    @Qualifier(value = "defaultStreamProducer")
    private IStreamProducer producer;

    @Autowired
    private IFileService fileService;

    @Autowired
    private FileConverter fileConverter;

    @Autowired
    private IFileChunkService fileChunkService;

    @Autowired
    private StorageEngine storageEngine;

    /**
     * 创建文件夹
     */
    @Override
    public Long createFolder(CreateFolderContext folderContext) {
        return saveUserFile(folderContext.getParentId(),
                folderContext.getFolderName(),
                FolderFlagEnum.YES,
                null,
                null,
                folderContext.getUserId(),
                null);
    }

    /**
     * 查询用户根目录文件信息
     */
    @Override
    public BPanUserFile getUserRootFile(Long userId) {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("user_id", userId);
        queryWrapper.eq("parent_id", FileConstants.TOP_PARENT_ID);
        queryWrapper.eq("folder_flag", FolderFlagEnum.YES.getCode());
        queryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        BPanUserFile userFile = getOne(queryWrapper);
        if (userFile == null) {
            throw new BPanBusinessException("找不到用户根目录文件");
        }
        return userFile;
    }

    /**
     * 查询用户的文件列表
     */
    @Override
    public List<BPanUserFileVO> getFileList(QueryFileListContext context) {
        return baseMapper.selectFileList(context);
    }

    /**
     * 更新文件名称
     */
    @Override
    public void updateFilename(UpdateFilenameContext context) {
        //1.校验文件名合法性
        checkUpdateFilenameCondition(context);
        //2.更新文件名
        doUpdateFilename(context);
    }

    /**
     * 批量删除文件
     */
    @Override
    public void deleteFiles(DeleteFileContext context) {
        //1.校验删除条件
        checkFileDeleteCondition(context);
        //2.批量删除文件
        doDeleteFile(context);
        //3.发布事件
        afterFileDelete(context);
    }

    /**
     * 文件秒传
     */
    @Override
    public boolean secUpload(SecUploadContext context) {
        //1.根据userId和identifier查询b_pan_file
        BPanFile entity = getFileByUserIdAndIdentifier(context.getUserId(), context.getIdentifier());
        //2.如果查不到，返回失败
        if (entity == null) {
            log.info("查不到文件");
            return false;
        }
        //3.查到则关联
        saveUserFile(context.getParentId(),
                context.getFilename(),
                FolderFlagEnum.NO,
                FileTypeEnum.getFileTypeCode(FileUtil.getFileSuffix(context.getFilename())),
                entity.getFileId(),
                context.getUserId(),
                entity.getFileSizeDesc());
        return true;
    }

    /**
     * 单文件上传
     */
    @Override
    public void upload(FileUploadContext context) {
        //1.保存b_pan_file，并保存到物理磁盘
        saveFile(context);
        //2.保存b_pan_user_file
        saveUserFile(
                context.getParentId(),
                context.getFilename(),
                FolderFlagEnum.NO,
                FileTypeEnum.getFileTypeCode(FileUtil.getFileSuffix(context.getFilename())),
                context.getRecord().getFileId(),
                context.getUserId(),
                context.getRecord().getFileSizeDesc()
        );
    }

    /**
     * 文件分片上传
     */
    @Override
    public FileChunkUploadVO chunkUpload(FileChunkUploadContext context) {
        FileChunkUploadVO vo = new FileChunkUploadVO();
        FileChunkSaveContext chunkSaveContext = fileConverter.fileChunkUploadContextToFileChunkSaveContext(context);
        // 调用文件模块的文件分片上传接口
        fileChunkService.saveChunkFile(chunkSaveContext);
        // chunkSaveContext中会保存mergeFlag
        vo.setMergeFlag(chunkSaveContext.getMergeFlagEnum().getCode());
        return vo;
    }

    /**
     * 查询用户已上传的文件分片列表
     */
    @Override
    public UploadedChunksVO getUploadedChunks(QueryUploadedChunksContext context) {
        QueryWrapper queryWrapper = Wrappers.query();
        //只查询一个字段
        queryWrapper.select("chunk_number");
        queryWrapper.eq("identifier", context.getIdentifier());
        queryWrapper.eq("create_user", context.getUserId());
        queryWrapper.gt("expiration_time", new Date());
        List<Integer> list = fileChunkService.listObjs(queryWrapper, value -> (Integer) value);
        UploadedChunksVO vo = new UploadedChunksVO();
        vo.setUploadChunks(list);
        return vo;
    }

    /**
     * 文件分片合并
     */
    @Override
    public void mergeFile(FileChunkMergeContext context) {
        //1.合并物理文件并且保存到文件表
        mergeFileChunkAndSaveFile(context);
        //2.保存文件用户关系映射
        Integer fileType = FileTypeEnum.getFileTypeCode(FileUtil.getFileSuffix(context.getFilename()));
        saveUserFile(
                context.getParentId(),
                context.getFilename(),
                FolderFlagEnum.NO,
                fileType,
                context.getRecord().getFileId(),
                context.getUserId(),
                context.getRecord().getFileSizeDesc());
    }

    /**
     * 下载文件
     */
    @Override
    public void download(FileDownloadContext context) {
        //1.校验文件: 文件是否存在 所属用户是否一致
        BPanUserFile record = getById(context.getFileId());
        checkOperationPermission(record, context.getUserId());
        //2.校验是否为文件夹
        if (checkIsFolder(record)) {
            throw new BPanBusinessException("文件夹暂不支持下载");
        }
        //3.执行下载的动作
        doDownload(record, context.getResponse());
    }

    /**
     * 下载文件但不需要校验用户(不需要所属用户才能下载)
     */
    @Override
    public void downloadWithoutUser(FileDownloadContext context) {
        //1.校验文件是否存在
        BPanUserFile record = getById(context.getFileId());
        if (Objects.isNull(record)) {
            throw new BPanBusinessException("文件不存在");
        }
        //2.校验是否为文件夹
        if (checkIsFolder(record)) {
            throw new BPanBusinessException("文件夹暂不支持下载");
        }
        //3.执行下载的动作
        doDownload(record, context.getResponse());
    }

    /**
     * 文件预览
     */
    @Override
    public void preview(FilePreviewContext context) {
        //1.校验文件: 文件是否存在 所属用户是否一致
        BPanUserFile userFile = getById(context.getFileId());
        checkOperationPermission(userFile, context.getUserId());
        //2.校验是否为文件夹
        if (checkIsFolder(userFile)) {
            throw new BPanBusinessException("文件夹暂不支持预览");
        }
        //3.执行预览动作
        doPreview(userFile, context.getResponse());
    }

    /**
     * 查询文件树
     */
    @Override
    public List<FolderTreeNodeVO> getFolderTree(FolderTreeNodeContext context) {
        //1.查询用户所有的文件
        List<BPanUserFile> folderRecords = queryFolderRecords(context.getUserId());
        //2.拼接文件树
        List<FolderTreeNodeVO> result = assembleFolderTreeNodeVOList(folderRecords);
        return result;
    }

    /**
     * 文件转移
     */
    @Override
    public void transfer(TransferFileContext context) {
        //1.权限校验
        checkTransferCondition(context);
        //2.具体转移
        doTransfer(context);
    }

    /**
     * 复制文件或文件夹
     */
    @Override
    public void copy(CopyFileContext context) {
        //1.权限校验
        checkCopyCondition(context);
        //2.复制操作
        doCopy(context);
    }

    /**
     * 文件搜索
     */
    @Override
    public List<FileSearchResultVO> search(FileSearchContext context) {
        //1.执行文件搜索
        List<FileSearchResultVO> result = doSearch(context);
        //2.拼接父文件名称
        fileParentFilename(result);
        //3.发布事件
        afterSearch(context);
        return result;
    }

    /**
     * 查询面包屑
     */
    @Override
    public List<BreadcrumbVO> getBreadcrumbs(BreadcrumbContext context) {
        //1.查询所有文件夹
        List<BPanUserFile> folderRecords = queryFolderRecords(context.getUserId());
        //2.转换为map
        Map<Long, BreadcrumbVO> prepareBreadcrumbVOMap = folderRecords.stream()
                .map(BreadcrumbVO::transfer)
                .collect(Collectors.toMap(BreadcrumbVO::getId, c -> c));
        //2.拼接面包屑
        BreadcrumbVO node;
        Long fileId = context.getFileId();
        List<BreadcrumbVO> result = Lists.newLinkedList();
        do {
            //从当前文件向上查
            node = prepareBreadcrumbVOMap.get(fileId);
            if (node != null) {
                result.add(0, node);
                //fileId变为parentId
                fileId = node.getParentId();
            }
        } while (node != null);
        return result;
    }

    /**
     * 递归查找子文件
     */
    @Override
    public List<BPanUserFile> findAllFileRecords(List<BPanUserFile> records) {
        List<BPanUserFile> result = Lists.newArrayList(records);
        //校验文件是否存在
        if (CollectionUtils.isEmpty(result)) {
            return result;
        }
        //过滤，得出文件夹个数不能为0
        long folderCount = result.stream().filter(r -> FolderFlagEnum.YES.getCode().equals(r.getFolderFlag())).count();
        if (folderCount == 0) {
            return result;
        }
        //每个文件都要递归查询
        records.stream().forEach(r -> doFindAllChildRecords(result, r));
        return result;
    }

    /**
     * 通过id列表递归查找子文件
     */
    @Override
    public List<BPanUserFile> findAllFileRecordsByFileIdList(List<Long> fileIdList) {
        if (CollectionUtils.isEmpty(fileIdList)) {
            return Lists.newArrayList();
        }
        List<BPanUserFile> records = listByIds(fileIdList);
        if (CollectionUtils.isEmpty(records)) {
            return Lists.newArrayList();
        }
        return this.findAllFileRecords(records);
    }

    /**
     * 将BPanUserFile集合转为BPanUserFileVO集合
     */
    @Override
    public List<BPanUserFileVO> transferVOList(List<BPanUserFile> records) {
        if (CollectionUtils.isEmpty(records)) {
            return Lists.newArrayList();
        }
        return records.stream().map(fileConverter::bPanUserFile2BPanUserFileVO).collect(Collectors.toList());
    }

    /*======================================private======================================*/

    /**
     * 递归查询子文件-具体实现
     */
    private void doFindAllChildRecords(List<BPanUserFile> result, BPanUserFile record) {
        //递归出口1——不存在
        if (Objects.isNull(record)) {
            return;
        }
        //递归出口2——非文件夹
        if (!checkIsFolder(record)) {
            return;
        }
        //查询子文件夹记录——fileId作为parentId查询文件
        List<BPanUserFile> childRecords = findChildRecordsIgnoreDelFlag(record.getFileId());
        //递归出口3——子文件不存在
        if (CollectionUtils.isEmpty(childRecords)) {
            return;
        }
        result.addAll(childRecords);
        //递归调用 需过滤掉非文件夹 只有文件夹才有子文件
        childRecords.stream().filter(r -> FolderFlagEnum.YES.getCode().equals(r.getFolderFlag()))
                .forEach(r -> doFindAllChildRecords(result, r));
    }

    /**
     * fileId作为parentId查询文件
     */
    private List<BPanUserFile> findChildRecordsIgnoreDelFlag(Long fileId) {
        QueryWrapper<BPanUserFile> queryWrapper = Wrappers.query();
        queryWrapper.eq("parent_id", fileId);
        return list(queryWrapper);
    }

    /**
     * 执行文件搜索
     */
    private List<FileSearchResultVO> doSearch(FileSearchContext context) {
        return baseMapper.searchFile(context);
    }

    /**
     * 拼接设置父文件名称
     */
    private void fileParentFilename(List<FileSearchResultVO> result) {
        if (CollectionUtils.isEmpty(result)) {
            return;
        }
        //提取parentId
        List<Long> parentIdList = result.stream().map(FileSearchResultVO::getParentId).collect(Collectors.toList());
        //查询文件
        List<BPanUserFile> parentRecords = listByIds(parentIdList);
        //提取为map，fileId->filename
        Map<Long, String> fileIdToFilenameMap = parentRecords.stream()
                .collect(Collectors.toMap(BPanUserFile::getFileId, BPanUserFile::getFilename));
        result.stream().forEach(c -> c.setParentFilename(fileIdToFilenameMap.get(c.getParentId())));
    }

    /**
     * 文件搜索后发布事件
     */
    private void afterSearch(FileSearchContext context) {
        FileSearchEvent event = new FileSearchEvent(context.getKeyword(), context.getUserId());
        producer.sendMessage(PanChannels.USER_SEARCH_OUTPUT, event);
    }

    /**
     * 复制文件或文件夹
     */
    private void doCopy(CopyFileContext context) {
        //1.获取要复制的文件夹
        List<BPanUserFile> prepareRecords = context.getPrepareRecords();
        //2.批量复制
        if (CollectionUtils.isNotEmpty(prepareRecords)) {
            List<BPanUserFile> allRecords = Lists.newArrayList();
            prepareRecords.stream().forEach(record -> assembleCopyChildRecord(allRecords, record,
                    context.getTargetParentId(), context.getUserId()));
            if (!saveBatch(allRecords)) {
                throw new BPanBusinessException("文件复制失败");
            }
        }
    }

    /**
     * 拼装当前文件记录以及所有子文件记录，保存到allRecords中
     */
    private void assembleCopyChildRecord(List<BPanUserFile> allRecords, BPanUserFile record, Long targetParentId, Long userId) {
        //1.新文件id
        Long newFileId = IdUtil.get();
        //2.旧文件id
        Long oldFileId = record.getFileId();
        record.setParentId(targetParentId);
        record.setFileId(newFileId);
        record.setUserId(userId);
        record.setCreateUser(userId);
        record.setUpdateUser(userId);
        record.setCreateTime(new Date());
        record.setUpdateTime(new Date());
        //3.重名处理
        handleDuplicateFileName(record);
        //4.添加到复制文件列表
        allRecords.add(record);
        //5.如果是文件夹，也要复制文件夹以及子文件夹的内容
        if (checkIsFolder(record)) {
            //5.1 查找下一级记录
            List<BPanUserFile> childRecords = findChildRecords(oldFileId);
            //5.2 当前查找没有下一级记录，说明没有文件夹了，不再进行复制
            if (CollectionUtils.isEmpty(childRecords)) {
                return;
            }
            //5.2 递归调用 目标id就是新的文件id(文件夹id)
            childRecords.stream().forEach(c -> assembleCopyChildRecord(allRecords, c, newFileId, userId));
        }
    }

    /**
     * 查找下一级的文件记录
     */
    private List<BPanUserFile> findChildRecords(Long parentId) {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("parent_id", parentId);
        queryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        return list(queryWrapper);
    }

    /**
     * 文件复制权限校验
     */
    private void checkCopyCondition(CopyFileContext context) {
        //1.目标文件夹必须是文件夹，而不是文件
        Long targetParentId = context.getTargetParentId();
        if (!checkIsFolder(getById(targetParentId))) {
            throw new BPanBusinessException("只能转移到文件夹");
        }
        //2.校验目标文件夹
        List<BPanUserFile> prepareRecords = listByIds(context.getFileIdList());
        context.setPrepareRecords(prepareRecords);
        if (checkIsChildFolder(prepareRecords, context.getTargetParentId(), context.getUserId())) {
            throw new BPanBusinessException("目标文件夹不能是当前的子文件夹");
        }
    }

    /**
     * 文件转移具体操作
     */
    private void doTransfer(TransferFileContext context) {
        List<BPanUserFile> prepareRecords = context.getPrepareRecords();
        //重新赋值需要更新的字段
        prepareRecords.stream().forEach(c -> {
            c.setParentId(context.getTargetParentId());
            c.setUserId(context.getUserId());
            c.setCreateUser(context.getUserId());
            c.setCreateTime(new Date());
            c.setUpdateUser(context.getUserId());
            c.setUpdateTime(new Date());
            handleDuplicateFileName(c);
        });
        //更新操作
        if (!updateBatchById(prepareRecords)) {
            throw new BPanBusinessException("文件转移失败");
        }
    }

    /**
     * 权限校验
     */
    private void checkTransferCondition(TransferFileContext context) {
        //1.目标文件夹必须是文件夹，而不是文件
        if (!checkIsFolder(getById(context.getTargetParentId()))) {
            throw new BPanBusinessException("只能转移到文件夹");
        }
        //2.校验目标文件夹
        List<BPanUserFile> prepareRecords = listByIds(context.getFileIdList());
        context.setPrepareRecords(prepareRecords);
        if (checkIsChildFolder(prepareRecords, context.getTargetParentId(), context.getUserId())) {
            throw new BPanBusinessException("目标文件夹不能是当前的子文件夹");
        }
    }

    /**
     * 校验目标文件夹是否为当前的子文件夹
     */
    private boolean checkIsChildFolder(List<BPanUserFile> prepareRecords, Long targetParentId, Long userId) {
        //1.过滤非文件夹
        prepareRecords = prepareRecords.stream().filter(record -> record.getFolderFlag().equals(FolderFlagEnum.YES.getCode()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(prepareRecords)) {
            return false;
        }
        //2.查询用户所有文件
        List<BPanUserFile> folderRecords = queryFolderRecords(userId);
        //3.对文件进行分组 parentId
        Map<Long, List<BPanUserFile>> folderRecordMap = folderRecords.stream()
                .collect(Collectors.groupingBy(BPanUserFile::getParentId));
        //4.筛选不可用的记录id
        List<BPanUserFile> unavailableFolderRecords = Lists.newArrayList();
        unavailableFolderRecords.addAll(prepareRecords);
        //4.1 收集不可用的文件夹
        prepareRecords.stream().forEach(c -> findAllChildFolderRecords(unavailableFolderRecords, folderRecordMap, c));
        List<Long> unavailableFolderRecordIds = unavailableFolderRecords.stream().map(BPanUserFile::getFileId)
                .collect(Collectors.toList());
        return unavailableFolderRecordIds.contains(targetParentId);
    }

    /**
     * 查询文件夹的所有子文件夹记录
     */
    private void findAllChildFolderRecords(List<BPanUserFile> unavailableFolderRecords,
                                           Map<Long, List<BPanUserFile>> folderRecordMap,
                                           BPanUserFile record) {
        if (record == null) {
            return;
        }
        //record的子文件夹
        List<BPanUserFile> childFolderRecords = folderRecordMap.get(record.getFileId());
        if (CollectionUtils.isEmpty(childFolderRecords)) {
            return;
        }
        //只要有子文件夹，就说明不可以用来转移文件
        unavailableFolderRecords.addAll(childFolderRecords);
        //record的子文件夹的子文件夹...
        childFolderRecords.stream().forEach(c -> findAllChildFolderRecords(unavailableFolderRecords, folderRecordMap, c));
    }

    /**
     * 拼接文件树
     */
    private List<FolderTreeNodeVO> assembleFolderTreeNodeVOList(List<BPanUserFile> folderRecords) {
        //1.校验非空
        if (CollectionUtils.isEmpty(folderRecords)) {
            return Lists.newArrayList();
        }
        //2.转换为VO
        List<FolderTreeNodeVO> mappedFolderTreeNodeVOList = folderRecords.stream()
                .map(fileConverter::bPanUserFileToFolderTreeNodeVO).collect(Collectors.toList());
        //3.以parentId分组，转换为Map
        Map<Long, List<FolderTreeNodeVO>> mappedFolderTreeNodeVOMap = mappedFolderTreeNodeVOList.stream()
                .collect(Collectors.groupingBy(FolderTreeNodeVO::getParentId));
        //4.遍历封装
        for (FolderTreeNodeVO node : mappedFolderTreeNodeVOList) {
            List<FolderTreeNodeVO> children = mappedFolderTreeNodeVOMap.get(node.getId());
            if (CollectionUtils.isNotEmpty(children)) {
                node.getChildren().addAll(children);
            }
        }
        //5.过滤，只需要父文件夹
        return mappedFolderTreeNodeVOList.stream().filter(node -> node.getParentId().equals(FileConstants.TOP_PARENT_ID))
                .collect(Collectors.toList());
    }

    /**
     * 查询用户所有的文件
     */
    private List<BPanUserFile> queryFolderRecords(Long userId) {
        QueryWrapper queryWrapper = Wrappers.query();
        queryWrapper.eq("user_id", userId);
        queryWrapper.eq("folder_flag", FolderFlagEnum.YES.getCode());
        queryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        return list(queryWrapper);
    }

    /**
     * 文件预览
     */
    private void doPreview(BPanUserFile userFile, HttpServletResponse response) {
        //1.查询文件的真实存储路径
        BPanFile bPanFile = fileService.getById(userFile.getRealFileId());
        if (bPanFile == null) {
            throw new BPanBusinessException("文件不存在");
        }
        //2.添加跨域的公共响应头
        addCommonResponseHeader(response, bPanFile.getFilePreviewContentType());
        //3.委托文件存储引擎读取文件内容到响应的输出流中
        realFileToOutputStream(bPanFile.getRealPath(), response);
    }

    /**
     * 校验文件: 文件是否存在 所属用户是否一致
     */
    private void checkOperationPermission(BPanUserFile record, Long userId) {
        if (record == null) {
            throw new BPanBusinessException("文件不存在");
        }
        if (!record.getUserId().equals(userId)) {
            throw new BPanBusinessException("您没有该文件的操作权限");
        }
    }

    /**
     * 校验是否为文件夹 true 文件夹 false 非文件夹
     */
    private boolean checkIsFolder(BPanUserFile record) {
        if (record == null) {
            throw new BPanBusinessException("文件不存在");
        }
        return FolderFlagEnum.YES.getCode().equals(record.getFolderFlag());
    }

    /**
     * 下载动作
     */
    private void doDownload(BPanUserFile record, HttpServletResponse response) {
        //1.查询文件的真实存储路径
        BPanFile bPanFile = fileService.getById(record.getRealFileId());
        if (bPanFile == null) {
            throw new BPanBusinessException("文件不存在");
        }
        //2.添加跨域的公共响应头
        addCommonResponseHeader(response, MediaType.APPLICATION_OCTET_STREAM_VALUE);
        //3.拼接下载文件的名称，长度等响应信息
        addDownloadAttribute(response, record, bPanFile);
        //4.委托文件存储引擎去读取文件内容到响应的输出流中
        realFileToOutputStream(bPanFile.getRealPath(), response);
    }


    /**
     * 添加跨域的公共响应头
     */
    private void addCommonResponseHeader(HttpServletResponse response, String contentType) {
        response.reset();
        HttpUtil.addCorsResponseHeaders(response);
        response.addHeader(FileConstants.CONTENT_TYPE_STR, contentType);
        response.setContentType(contentType);
    }

    /**
     * 拼接下载文件的名称，长度等响应信息
     */
    private void addDownloadAttribute(HttpServletResponse response, BPanUserFile record, BPanFile bPanFile) {
        try {
            response.addHeader(FileConstants.CONTENT_DISPOSITION_STR,
                    FileConstants.CONTENT_DISPOSITION_VALUE_PREFIX_STR
                            + new String(record.getFilename().getBytes(FileConstants.GB2312_STR),
                            FileConstants.IOS_8859_1_STR));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 委托文件存储引擎去读取文件内容到响应的输出流中
     */
    private void realFileToOutputStream(String realPath, HttpServletResponse response) {
        try {
            ReadFileContext context = new ReadFileContext();
            context.setRealPath(realPath);
            context.setOutputStream(response.getOutputStream());
            storageEngine.readFile(context);
        } catch (IOException e) {
            e.printStackTrace();
            throw new BPanBusinessException("文件下载失败");
        }
    }

    /**
     * 合并物理文件并且保存到文件表
     */
    private void mergeFileChunkAndSaveFile(FileChunkMergeContext context) {
        FileChunkMergeAndSaveContext mergeAndSaveContext = fileConverter.fileChunkMergeContextToFileChunkMergeAndSaveContext(context);
        fileService.mergeFileChunkAndSaveFile(mergeAndSaveContext);
        context.setRecord(mergeAndSaveContext.getRecord());
    }

    /**
     * 保存用户文件
     */
    private Long saveUserFile(Long parentId, String fileName, FolderFlagEnum folderFlagEnum,
                              Integer fileType, Long realFileId, Long userId, String fileSizeDesc) {
        //1.转换为entity
        BPanUserFile entity = assembleBPanUserFile(
                parentId,
                fileName,
                folderFlagEnum,
                fileType, realFileId,
                userId,
                fileSizeDesc);
        //2.保存到表b_pan_user_file中
        if (!save(entity)) {
            throw new BPanBusinessException("保存文件信息失败");
        }
        return entity.getFileId();
    }

    //转换为entity
    private BPanUserFile assembleBPanUserFile(Long parentId, String fileName, FolderFlagEnum folderFlagEnum,
                                              Integer fileType, Long realFileId, Long userId, String fileSizeDesc) {
        //1.赋值
        BPanUserFile file = new BPanUserFile();
        file.setFileId(IdUtil.get());
        file.setUserId(userId);
        file.setParentId(parentId);
        file.setRealFileId(realFileId);
        file.setFilename(fileName);
        file.setFolderFlag(folderFlagEnum.getCode());
        file.setFileSizeDesc(fileSizeDesc);
        file.setFileType(fileType);
        file.setDelFlag(DelFlagEnum.NO.getCode());
        file.setCreateUser(userId);
        file.setCreateTime(new Date());
        file.setUpdateUser(userId);
        file.setUpdateTime(new Date());
        //2.处理重复名称
        handleDuplicateFileName(file);
        return file;
    }

    /**
     * 处理重复名称
     */
    private void handleDuplicateFileName(BPanUserFile entity) {
        //1.获取原文件名
        String filename = entity.getFilename();
        String newFilenameWithoutSuffix = "";
        String newFilenameSuffix = "";
        int newFilenamePointPosition = filename.lastIndexOf(BPanConstants.POINT_STR);
        //2.如果位置是-1，表示没有后缀名
        if (newFilenamePointPosition == BPanConstants.MINUS_ONE_INT) {
            newFilenameWithoutSuffix = filename;
            //没有后缀名，设后缀为空
            newFilenameSuffix = StringUtils.EMPTY;
            log.info("无后缀名:{}", newFilenameWithoutSuffix);
        } else {
            //有后缀名，需要截取前缀即文件名
            newFilenameWithoutSuffix = filename.substring(BPanConstants.ZERO_INT, newFilenamePointPosition);
            //获取后缀名
            newFilenameSuffix = filename.replace(newFilenameWithoutSuffix, StringUtils.EMPTY);
            log.info("有后缀名:{}，后缀是:{}", newFilenameWithoutSuffix, newFilenameSuffix);
        }
        //3.统计同名文件的数量
        int count = getDuplicateFilename(entity, newFilenameWithoutSuffix);
        log.info("同名文件数量:{}", count);
        //4.如果没有重复文件名就不用处理
        if (count == 0) {
            return;
        }
        //5.处理重复文件名
        String newFilename = assembleNewFilename(newFilenameWithoutSuffix, count, newFilenameSuffix);
        entity.setFilename(newFilename);
    }

    /**
     * 处理重复文件名
     */
    private String assembleNewFilename(String newFilenameWithoutSuffix, int count, String newFilenameSuffix) {
        //张三.jpg -> （张三1）.jpg
        String newFilename = new StringBuilder(newFilenameWithoutSuffix)
                .append(FileConstants.CN_LEFT_PARENTHESES_STR)
                .append(count)
                .append(FileConstants.CN_RIGHT_PARENTHESES_STR)
                .append(newFilenameSuffix)
                .toString();
        log.info("拼接后的新文件名:{}", newFilename);
        return newFilename;
    }

    /**
     * 统计同名文件的数量
     */
    private int getDuplicateFilename(BPanUserFile entity, String newFilenameWithoutSuffix) {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("parent_id", entity.getParentId());
        queryWrapper.eq("folder_flag", entity.getFolderFlag());
        queryWrapper.eq("user_id", entity.getUserId());
        queryWrapper.eq("del_flag", entity.getDelFlag());
        queryWrapper.likeLeft("filename", newFilenameWithoutSuffix);
        return count(queryWrapper);
    }

    /**
     * 校验文件名合法性
     */
    private void checkUpdateFilenameCondition(UpdateFilenameContext context) {
        //1.查询文件名是否存在
        Long fileId = context.getFileId();
        BPanUserFile entity = getById(fileId);
        if (entity == null) {
            throw new BPanBusinessException("该文件不存在");
        }
        //2.不能修改别人的文件名
        if (!context.getUserId().equals(entity.getUserId())) {
            throw new BPanBusinessException("不能修改别人的文件名");
        }
        //3.新旧文件名不能相同
        if (context.getNewFilename().equals(entity.getFilename())) {
            throw new BPanBusinessException("新旧文件名不能相同");
        }
        //4.同级文件的名称不能相同
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("parent_id", entity.getParentId());
        queryWrapper.eq("filename", context.getNewFilename());
        int count = count(queryWrapper);
        if (count > 0) {
            throw new BPanBusinessException("该文件名已被占用");
        }
        context.setEntity(entity);
    }

    /**
     * 更新文件名
     */
    private void doUpdateFilename(UpdateFilenameContext context) {
        BPanUserFile entity = context.getEntity();
        String newFilename = context.getNewFilename();

        entity.setFilename(newFilename);
        entity.setUpdateUser(context.getUserId());
        entity.setUpdateTime(new Date());

        if (!updateById(entity)) {
            throw new BPanBusinessException("更改文件名失败!");
        }
    }

    /**
     * 校验删除条件
     */
    private void checkFileDeleteCondition(DeleteFileContext context) {
        //文件存在
        List<Long> fileIdList = context.getFileIdList();
        List<BPanUserFile> dbUserFileList = listByIds(fileIdList);
        if (fileIdList.size() != dbUserFileList.size()) {
            throw new BPanBusinessException("文件不存在");
        }
        //不能删除别人的文件
        Set<Long> userSet = dbUserFileList.stream().map(BPanUserFile::getUserId).collect(Collectors.toSet());
        if (userSet.size() != 1) {
            throw new BPanBusinessException("不能删除别人的文件");
        }
        if (!userSet.stream().findFirst().get().equals(context.getUserId())) {
            throw new BPanBusinessException("不能删除别人的文件");
        }
        //保证不重复删除同个文件
        Set<Long> fileIdSet = dbUserFileList.stream().map(BPanUserFile::getFileId).collect(Collectors.toSet());
        fileIdSet.addAll(fileIdList);
        if (fileIdSet.size() != fileIdList.size()) {
            throw new BPanBusinessException("不能重复删除同个");
        }
    }

    /**
     * 批量删除文件
     */
    private void doDeleteFile(DeleteFileContext context) {
        List<Long> fileIdList = context.getFileIdList();
        UpdateWrapper updateWrapper = new UpdateWrapper();
        updateWrapper.eq("user_id", context.getUserId());
        updateWrapper.in("file_id", fileIdList);
        updateWrapper.set("update_time", new Date());
        updateWrapper.set("del_flag", DelFlagEnum.YES.getCode());
        if (!update(updateWrapper)) {
            throw new BPanBusinessException("删除文件失败");
        }
    }

    /**
     * 发布事件
     */
    private void afterFileDelete(DeleteFileContext context) {
        DeleteFileEvent deleteFileEvent = new DeleteFileEvent(context.getFileIdList());
        log.info("发布删除文件事件, fileIdList:{}", context.getFileIdList());
        producer.sendMessage(PanChannels.DELETE_FILE_OUTPUT, deleteFileEvent);
    }

    /**
     * 根据userId和identifier查询b_pan_file
     */
    private BPanFile getFileByUserIdAndIdentifier(Long userId, String identifier) {
        QueryWrapper queryWrapper = Wrappers.query();
        queryWrapper.eq("create_user", userId);
        queryWrapper.eq("identifier", identifier);
        List<BPanFile> list = fileService.list(queryWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        return list.get(0);
    }

    /**
     * 保存b_pan_file
     */
    private void saveFile(FileUploadContext fileUploadContext) {
        FileSaveContext fileSaveContext = fileConverter.fileUploadContextToFileSaveContext(fileUploadContext);
        fileService.saveFile(fileSaveContext);
        fileUploadContext.setRecord(fileSaveContext.getRecord());
    }

}
